package pl.com.qapps.datastore.berkeley;

import static pl.com.qapps.datastore.berkeley.impl.EntityBindings.ENTITY_KEY_BINDING;

import java.io.IOException;

import pl.com.qapps.datastore.BytesHelper;
import pl.com.qapps.datastore.GaeHelper;


import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.sleepycat.je.DatabaseEntry;

public class SerializationPerformanceTest {

	static long NUM = 1000L;
	static Test<Key> bytesTest = new Test<Key>(new Serializer<Key>() {

		@Override
		public byte[] serialize(Key value) {
			try {
				return BytesHelper.serialize(value);
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}

		@Override
		public Key deserialize(byte[] bytes) {
			try {
				return BytesHelper.toKey(bytes);
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}

	}) {
		@Override
		public String toString() {
			return "my own bytes serializer";
		}
	};
	static Test<Key> tupleTest = new Test<Key>(new Serializer<Key>() {

		DatabaseEntry entry = new DatabaseEntry();

		@Override
		public byte[] serialize(Key value) {
			entry = new DatabaseEntry();
			ENTITY_KEY_BINDING.objectToEntry(value, entry);
			return entry.getData();
		}

		@Override
		public Key deserialize(byte[] bytes) {
			return ENTITY_KEY_BINDING.entryToObject(new DatabaseEntry(bytes));
		}

	}) {
		public String toString() {
			return "custom tuplebinding serializer";
		}
	};

	public static void main(String[] args) {
		GaeHelper.registerEnvironment();

		// String bytes = "my own bytes serializer";
		// String tuple = "custom tuplebinding serializer";

		Key rootString = KeyFactory
				.createKey("Person", "This is unique person");
		Key rootLong = KeyFactory.createKey("Person", 4568412315L);

		Key smallString = KeyFactory.createKey("Another", "unique");
		Key largeString = KeyFactory
				.createKey(
						"Another",
						"unique and nobode else is like it otherwise it is an illegal state and exception should be thrown");

		Key smallLong = KeyFactory.createKey("Person", 456456L);
		Key largeLong = KeyFactory.createKey("Person", 1896518675158956132L);

		Key descendant4 = KeyFactory.createKey(KeyFactory.createKey(KeyFactory
				.createKey(KeyFactory.createKey(
						KeyFactory.createKey("Person", 56456L), "Person",
						46456L), "Person", 45456L), "Person", 45656L),
				"Person", 45645L);
		Key descendant8 = KeyFactory.createKey(KeyFactory.createKey(KeyFactory
				.createKey(
						KeyFactory.createKey(descendant4, "Descendant", 903L),
						"Descendant", 903L), "Descendant", 902L), "Descendant",
				901L);

		byte[] b = tupleTest.getSerializer().serialize(rootString);
		System.out.println(tupleTest.getSerializer().deserialize(b));

		run(rootString, bytesTest);
		run(rootString, tupleTest);

		run(rootLong, bytesTest);
		run(rootLong, tupleTest);

		run(smallString, bytesTest);
		run(smallString, tupleTest);

		run(largeString, bytesTest);
		run(largeString, tupleTest);

		run(smallLong, bytesTest);
		run(smallLong, tupleTest);

		run(largeLong, bytesTest);
		run(largeLong, tupleTest);

		run(descendant4, bytesTest);
		run(descendant4, tupleTest);

		run(descendant8, bytesTest);
		run(descendant8, tupleTest);

	}

	static void run(Key key, Test<Key> test) {
		Results results = test.run(NUM, key);
		System.out.println("Test: " + test);
		printResults(new KeyDesc(key).toString(), results);
	}

	static void printResults(String desc, Results results) {
		System.out.println(desc);
		System.out.println("	quantity: " + results.getAmount());
		System.out.println("	serialization: " + results.getSerializationTotal()
				+ " milisecs");
		System.out.println("	average: " + results.getSerializationAverage()
				+ " nanosecs");
		System.out.println("	deserialization: "
				+ results.getDeserializationTotal() + " milisecs");
		System.out.println("	average: " + results.getDeserailizationAverage()
				+ " nanosecs");
		System.out.println();
	}

	interface Serializer<T> {
		byte[] serialize(T value);

		T deserialize(byte[] bytes);
	}

	static class Test<T> {
		private final Serializer<T> serializer;

		Test(Serializer<T> serializer) {
			super();
			this.serializer = serializer;
		}

		final Results run(long amount, T value) {
			long sTotal = 0, dTotal = 0;
			long start, stop;
			byte[] bytes;
			for (long i = 0; i < amount; i++) {
				start = System.currentTimeMillis();
				bytes = serializer.serialize(value);
				stop = System.currentTimeMillis();
				sTotal += (stop - start);

				start = System.currentTimeMillis();
				value = serializer.deserialize(bytes);
				stop = System.currentTimeMillis();
				dTotal += (stop - start);
			}
			return new Results(sTotal, dTotal, amount);
		}

		Serializer<T> getSerializer() {
			return this.serializer;
		}
	}

	static class Results {
		private long serializationTotal;
		private long deserializationTotal;
		private long serializationAverage;
		private long deserailizationAverage;
		private long amount;

		public Results(long serializationTotal, long deserializationTotal,
				long amount) {
			super();
			this.serializationTotal = serializationTotal;
			this.deserializationTotal = deserializationTotal;
			this.amount = amount;
			this.serializationAverage = (serializationTotal * 1000L) / amount;
			this.deserailizationAverage = (deserializationTotal * 1000L)
					/ amount;
		}

		public long getDeserializationTotal() {
			return deserializationTotal;
		}

		public long getSerializationTotal() {
			return serializationTotal;
		}

		public long getSerializationAverage() {
			return serializationAverage;
		}

		public long getDeserailizationAverage() {
			return deserailizationAverage;
		}

		public long getAmount() {
			return amount;
		}

		@Override
		public String toString() {
			return "";
		}

	}

	static class KeyDesc {
		private Key key;

		public KeyDesc(Key key) {
			this.key = key;
		}

		@Override
		public String toString() {
			return "	serialized object: " + key;
		}
	}
}
